package com.selftest.web.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.PropertyPreFilter;
import com.alibaba.fastjson.serializer.SerializeFilter;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.selftest.web.RequestHandlerHolder;
import com.selftest.web.annotation.Ignore;
import com.selftest.web.annotation.ResponseJSON;
import com.selftest.web.filter.EntityClassPropertyFilter;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * JSON 数据转换，实现返回体字段过滤
 */
public class JSONHttpMessageConverter extends FastJsonHttpMessageConverter implements ApplicationContextAware {

    /**
     * 应用上下文
     */
    private ApplicationContext ctx;

    /**
     * 请求处理器 Handler 持有者
     */
    @Resource
    private RequestHandlerHolder requestHandlerHolder;

    private FastJsonConfig fastJsonConfig = new FastJsonConfig();

    /**
     * 返回体处理
     * @param obj the object to write to the output message
     * @param outputMessage the HTTP output message to write to
     * @throws IOException
     * @throws HttpMessageNotWritableException
     */
    @Override
    protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //获取到返回流
        OutputStream out = outputMessage.getBody();
        String jsonString = toJSONString(obj);
        out.write(jsonString.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 将对象转换为JSON字符串
     * @return
     */
    private String toJSONString(Object obj){
        //获取到处理请求的 Handler
        if(Objects.isNull(requestHandlerHolder)){
            return JSON.toJSONString(obj);
        }
        Object handler = requestHandlerHolder.getHandler();
        //如果 handler 为空或者 handler 不为空但是返回类型不是 entity 的
        if(Objects.isNull(handler)
                || (Objects.nonNull(requestHandlerHolder.getHandlerMethod().getReturnType())
                    && requestHandlerHolder.getHandlerMethod().getReturnType().isAssignableFrom(ResponseEntity.class))){
            return JSON.toJSONString(obj);
        }

        //如果是处理方法，则获取到返回类型 ReturnType、annotation
        ResponseJSON annotation = null;
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            MethodParameter returnType = method.getReturnType();
            annotation = returnType.getMethodAnnotation(ResponseJSON.class);
            if (Objects.isNull(annotation)) {
                annotation = method.getMethodAnnotation(ResponseJSON.class);
            }
        }
        if(Objects.isNull(annotation) || (Objects.nonNull(annotation) && !annotation.enable())){
            return Objects.nonNull(obj) ? JSON.toJSONString(obj) : null;
        }
        // 获取到真正实现返回体过滤的序列化 filter
        SerializeFilter filter = getSerializeFilter(annotation);
        // 封装自定义 filter, 传入上面的 filter 和 自定义注解
        EntityClassPropertyFilter propertyFilter = new EntityClassPropertyFilter(filter, annotation);
        return JSON.toJSONString(obj, propertyFilter, this.fastJsonConfig.getSerializerFeatures());
    }

    /**
     * 获取序列化 filter
     * @param annotation
     * @return
     */
    private SerializeFilter getSerializeFilter(ResponseJSON annotation) {
        if(Objects.nonNull(annotation)){
            Ignore[] ignoreFields = annotation.ignore();
            if(ignoreFields.length == 0){
                return null;
            }
            Map<Class<?>, Map<Boolean, Set<String>>> ignoreMap = new HashMap<>();
            for (Ignore ignore : ignoreFields) {
                Class<?> declaringClass = ignore.declaringClass();
                Map<Boolean, Set<String>> propNameMap = ignoreMap.get(declaringClass);
                if(Objects.isNull(propNameMap)) {
                    propNameMap = new HashMap<>();
                    ignoreMap.put(declaringClass, propNameMap);
                }
                boolean inverse = ignore.inverse();
                Set<String> propNameSet = propNameMap.get(inverse);
                if(CollectionUtils.isEmpty(propNameSet)){
                    propNameSet = new HashSet<>();
                    propNameMap.put(inverse, propNameSet);
                }
                for (String propName : ignore.propNames()) {
                    propNameSet.add(propName);
                }
            }
            // 返回 属性预处理 Filter 实例，真正实现返回体字段过滤
            return (PropertyPreFilter) (jsonSerializer, object, name) -> {
                for (Map.Entry<Class<?>, Map<Boolean, Set<String>>> ignoreEntry : ignoreMap.entrySet()) {
                    if (ignoreEntry.getKey().isAssignableFrom(object.getClass())) {
                        Set<String> ignorePropNames = ignoreEntry.getValue().get(false);
                        if (Objects.nonNull(ignorePropNames) && ignorePropNames.contains(name)) {
                            return false;
                        }
                        ignorePropNames = ignoreEntry.getValue().get(true);
                        if (Objects.nonNull(ignorePropNames) && !ignorePropNames.contains(name)) {
                            return false;
                        }
                    }
                }
                return true;
            };
        }
        return null;
    }

    /**
     * 获取应用上下文
     * @param applicationContext the ApplicationContext object to be used by this object
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
}
